package com.example.snakefinal;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaRecorder;
import android.media.SoundPool;
import android.os.Build;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import androidx.annotation.NonNull;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

@SuppressLint("ViewConstructor")
public class SnakeGame extends SurfaceView implements Runnable {

    // Objects for the game loop/thread
    private Thread mThread = null;
    // Control pausing between updates
    private long mNextFrameTime;
    // Is the game currently playing and or paused?
    private volatile boolean mPlaying = false;
    public volatile GAME_STATE CURRENT_STATE = GAME_STATE.START_SCREEN;
    private Bitmap startScreen;
    private Bitmap helpScreen;
    private Bitmap pauseScreen;
    private Bitmap gameOverScreen;
    private Bitmap backGround;
    private Bitmap border;

    // for playing sound effects
    //make soundn abstract class
    private SoundPool mSP;
    private int mEat_ID = -1;
    private int mCrashID = -1;
    private int mSongID = -1;

    private final SurfaceHolder mSurfaceHolder;
    private final Paint mPaint;
    final Snake mSnake;
    private final List<RobotSnake> robotSnakes;
    private FoodFactory foodFactory;
    public final List<Food> FOOD;
    // 帧率
    private final long TARGET_FPS = 60;
    final long MILLIS_PER_SECOND = 1000;
    // 贪吃蛇长宽度
    private final int blockSize;
    public final Point screenSize;
    private final Context gameContext;
    private PauseButton pauseButton;

    public final List<Snake> snakes;

    private int wayMethod;

    // 天气系统 0 无天气，1 雾天，2黑夜
    int weather = 0;
    // 持续帧数
    private int weatherFps;
    private int weatherFpsTmp;
    List<Point> walls = new ArrayList<>();
    List<Point> huos = new ArrayList<>();

    private Bitmap wall;
    private Bitmap huo;

    // 0 单人游戏，1 多人主机，2多人客机
    private int multiplayer;

    // This is the constructor method that gets called
    // from SnakeActivity
    public SnakeGame(Context context, Point size, int multiplayer) {
        super(context);
        this.multiplayer = multiplayer;
        int[] skinResourceArray = new int[]{R.drawable.body3, R.drawable.body4, R.drawable.body6, R.drawable.body7, R.drawable.body8, R.drawable.body9, R.drawable.body10, R.drawable.body11, R.drawable.body5};
        screenSize = size;
        gameContext = context;
        // 字体宽度
        this.blockSize = screenSize.x / 16;
        loadBackground(context);
        loadScreens(context);
        initializeSoundPool(context);

        FOOD = new CopyOnWriteArrayList<>();
        mSurfaceHolder = getHolder();
        mPaint = new Paint();
        mSnake = makeSnake(context);
        mSnake.setPaint(mPaint);
        robotSnakes = new ArrayList<>();
        // 单人游戏新增机器人
        if (multiplayer != 2) {
            Random random = new Random();
            for (int i = 0; i < 2; i++) {
                // 屏幕大小
                Point mr = new Point(screenSize.x, screenSize.y);
                int i1 = random.nextInt(skinResourceArray.length);
                System.out.println(i1);
                RobotSnake robotSnake = new RobotSnake(context, mr, skinResourceArray[i1]);
                robotSnake.setSnakeGame(this);
                robotSnake.setPaint(new Paint());
                robotSnake.name = "robot" + (i + 1);
                Thread thread = new Thread(robotSnake);
                thread.setDaemon(true);
                thread.start();
                robotSnakes.add(robotSnake);
            }
        }
        snakes = getSnakes();
    }

    AudioRecordStart audioRecordStart;

    public void initWayMethod(int wayMethod) {
        // 屏幕大小
        this.wayMethod = wayMethod;
        if (wayMethod == 3) {
            audioRecordStart = new AudioRecordStart(gameContext, MediaRecorder.AudioSource.MIC, mSnake);
        }

    }

    // Called to start a new game
    public void newGame() {
        // reset the snake
        mSnake.reset();
        for (RobotSnake robotSnake : robotSnakes) {
            robotSnake.reset();
        }
        FOOD.clear();
        // 新增一个食物在中间
        FOOD.add(new Apple(gameContext, new Point(screenSize.x / 2 + 200, screenSize.y / 2), Const.SIZE));
        // # of food types used for random spawning
        foodFactory = new FoodFactory();

        for (int i = 0; i < robotSnakes.size(); i++) {
            foodFactory.createFood(FOOD, 0, gameContext, Const.SIZE, snakes);
        }
        // 墙壁
        for (int i = 0; i < 20 + new Random().nextInt(20); i++) {
            walls.add(new Point(100 + new Random().nextInt(Const.MAP_W - 200), 100 + new Random().nextInt(Const.MAP_H - 200)));
        }
        // 火焰
        for (int i = 0; i < 4 + new Random().nextInt(6); i++) {
            huos.add(new Point(100 + new Random().nextInt(Const.MAP_W - 200), 100 + new Random().nextInt(Const.MAP_H - 200)));
        }
        // Reset the mScore
        // Setup mNextFrameTime so an update can triggered
        mNextFrameTime = System.currentTimeMillis();
    }

    @NonNull
    private List<Snake> getSnakes() {
        List<Snake> snakes = new ArrayList<>(robotSnakes);
        snakes.add(mSnake);
        return snakes;
    }

    // Handles the game loop
    @Override
    public void run() {
        while (mPlaying) {
            if (updateRequired()) {
                if (CURRENT_STATE == GAME_STATE.PLAYING_SCREEN) {
                    if (multiplayer == 0 || multiplayer == 1) {
                        update();
                    }
                }
                draw();
            }
        }
    }

    // Check to see if it is time for an update
    // 检查帧数
    public boolean updateRequired() {
        // Are we due to update the frame
        if (mNextFrameTime <= System.currentTimeMillis()) {
            // Setup when the next update will be triggered
            mNextFrameTime = System.currentTimeMillis() + MILLIS_PER_SECOND / TARGET_FPS;
            // Return true so that the update and draw
            // methods are executed
            return true;
        }
        return false;
    }

    // Update all the game objects
    public void update() {
        // 减少一次
        if (weatherFps-- <= 0) {
            // 从打雷切换到夜晚模式
            if (weather == 11) {
                weather = 1;
                weatherFps = weatherFpsTmp;
            } else {
                // 随机天气
                weather = new Random().nextInt(3);
                // 天气持续帧数
                weatherFps = 120;
            }
        }
        // 夜晚模式
        if (weather == 1) {
            // 从夜晚模式切换到打雷
            if (new Random().nextInt(10) > 8) {
                weather = 11;
                // 10 打雷持续帧数
                weatherFpsTmp = weatherFps - 10;
                // 10 打雷持续帧数
                weatherFps = 10;
            }
        }
        // 更新移动信息
        for (Snake snake : snakes) {
            snake.move();
            dinnerHandler(snake);
            // 机器人死掉不影响剧情。
            if (snake instanceof RobotSnake) {
                if (snake.detectDeath() || detectDeath(snake)) {
                    snake.reset();
                }
            }
        }
        // 监测是否噶掉。
        if (mSnake.detectDeath() || detectDeath(mSnake)) {
            // Pause the game ready to start again
            if (mCrashID > 0) {
                mSP.play(mCrashID, 1, 1, 0, 0, 1);
            }
            CURRENT_STATE = GAME_STATE.GAME_OVER_SCREEN;
        }
    }

    boolean detectDeath(Snake snake) {
        for (Point point : walls) {
            if (Snake.checkBound(point, snake.getSegmentLocations().get(0))) {
                return true;
            }
        }
        for (Point point : huos) {
            if (Snake.checkBound(point, snake.getSegmentLocations().get(0))) {
                return true;
            }
        }
        return false;
    }

    private void dinnerHandler(Snake snake) {
        // 监测是否吃掉食物。
        for (int i = 0; i < FOOD.size(); i++) {
            Food food = FOOD.get(i);
            if (snake.checkDinner(food)) {
                FOOD.remove(food);
                for (int j = 0; j < food.getValue(); j++) {
                    // 增加一节身体
                    snake.getSegmentLocations().add(new Point(-1000, -1000));
                }
                food.applyMod(this, snake);
                if (mEat_ID > 0) {
                    mSP.play(mEat_ID, 1, 1, 0, 0, 1);
                }
                foodFactory.createFood(FOOD, getMaxScore(), getContext(), Const.SIZE, snakes);
            }
        }
    }

    // 游戏场景：虚拟场景 16 * 9
    Bitmap bitmap = Bitmap.createBitmap(Const.MAP_W, Const.MAP_H, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    Bitmap newBitmap = Bitmap.createBitmap(Const.MAP_W, Const.MAP_H, Bitmap.Config.ARGB_8888);
    Canvas newCanvas = new Canvas(newBitmap);

    // Do all the drawing
    public void draw() {
        // Get a lock on the mCanvas
        if (mSurfaceHolder.getSurface().isValid()) {
            mPaint.setTextAlign(Paint.Align.CENTER);
            // Draw the score
            mPaint.setColor(Color.argb(255, 136, 255, 239));
            mPaint.setTextSize(blockSize);
            // Objects for drawing
            // 展示场景
            Canvas mCanvas = mSurfaceHolder.lockCanvas();
            //--------------------绘制场景

            Point head = new Point(screenSize.x / 2, screenSize.y / 2);
            if (!mSnake.getSegmentLocations().isEmpty()) {
                head = mSnake.getSegmentLocations().get(0);
            }

            // Fill the screen with a color 绘制背景。
            canvas.drawBitmap(backGround, null, new Rect(0, 0, Const.MAP_W, Const.MAP_H), null);

            for (Point point : huos) {
                canvas.drawBitmap(huo, point.x, point.y, null);
            }

            for (Point point : walls) {
                canvas.drawBitmap(wall, point.x, point.y, null);
            }
            // Draw the game border 绘制边界。
            // canvas.drawBitmap(border, null, new Rect(0, 0, Const.W * blockSize * 2, Const.H * blockSize * 2), null);
            // 绘制食物
            for (int i = 0; i < FOOD.size(); i++) {
                Bitmap mBitmap = FOOD.get(i).mBitmap;
                Point location = FOOD.get(i).getLocation();
                canvas.drawBitmap(mBitmap, location.x, location.y, null);
            }
            mSnake.draw(canvas);
            robotSnakes.forEach(item -> {
                item.draw(canvas);
            });
            if (weather == 1) {
                // 夜晚模式
                canvas.drawColor(Color.DKGRAY, PorterDuff.Mode.MULTIPLY);
            }
            if (weather == 2) {
                // 战争迷雾
                newCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
                ArrayList<Point> segmentLocations = mSnake.getSegmentLocations();
                for (Point segmentLocation : segmentLocations) {
                    int x = segmentLocation.x;
                    int y = segmentLocation.y;
                    newCanvas.drawOval(x - 400, y - 400, x + 400, y + 400, new Paint());
                }
                newCanvas.drawColor(Color.BLACK, PorterDuff.Mode.SRC_OUT);
                newCanvas.save();
                canvas.drawBitmap(newBitmap, 0, 0, null);
            }

            // 截取绘画场景的一部分至展示场景
            canvas.save();
            Rect src = getViewRect(head);
            mCanvas.drawBitmap(bitmap, src, new Rect(0, 0, screenSize.x, screenSize.y), null);

            //--------------------绘制场景

            // 分数
            mCanvas.drawText("" + mSnake.getScore() / 10, blockSize * 2, blockSize, mPaint);
            // 暂停按钮
            pauseButton.draw(mCanvas, mPaint);
            // 生命值
            drawLives(mSnake, mCanvas);
            if (mSnake.getHighScore() > 0) {
                mCanvas.drawText("High Score: " + mSnake.getHighScore(), screenSize.x / 2f, blockSize, mPaint);
            }
            mCanvas.drawText("当前天气：" + weather + "," + weatherFps, screenSize.x - 900, 100, mPaint);

            List<Snake> collect = snakes.stream().sorted(Comparator.comparingInt(Snake::getScore)).collect(Collectors.toList());
            for (int i = 0; i < collect.size(); i++) {
                Snake item = collect.get(i);
                Paint paint = new Paint();
                paint.setColor(Color.BLACK);
                paint.setTextSize(100);
                mCanvas.drawText(item.name + ":" + item.getScore(), screenSize.x - 500, i * 100 + 350, mPaint);
            }
            Bitmap backButton = BitmapFactory.decodeResource(getResources(), R.drawable.backbutton);
            backButton = Bitmap.createScaledBitmap(backButton, blockSize * 2, blockSize, false);
            mCanvas.drawBitmap(backButton, 350, 10, mPaint);  // Draw in the top-left corner


            switch (CURRENT_STATE) {
                case START_SCREEN:
                    startScreen(mCanvas);
                    break;
                case TUTORIAL_SCREEN:
                    tutorialScreen(mCanvas);
                    break;
                case PAUSE_SCREEN:
                    pauseScreen(mCanvas);
                    break;
                case GAME_OVER_SCREEN:
                    deathScreen(mCanvas);
                    break;
            }
            // Unlock the mCanvas and reveal the graphics for this frame
            mSurfaceHolder.unlockCanvasAndPost(mCanvas);
        }
    }

    @NonNull
    private Rect getViewRect(Point head) {
        int startX = 0;
        int startY = 0;
        int endX;
        int endY;

        int minX = screenSize.x / 2;
        int maxX = Const.MAP_W - minX;

        int minY = screenSize.y / 2;
        int maxY = Const.MAP_H - minY;

        if (head.x <= minX) {
            endX = screenSize.x;
        } else if (head.x >= maxX) {
            startX = Const.MAP_W - screenSize.x;
            endX = Const.MAP_W;
        } else {
            startX = head.x - screenSize.x / 2;
            endX = head.x + screenSize.x / 2;
        }

        if (head.y <= minY) {
            endY = screenSize.y;
        } else if (head.y >= maxY) {
            startY = Const.MAP_H - screenSize.y;
            endY = Const.MAP_H;
        } else {
            startY = head.y - screenSize.y / 2;
            endY = head.y + screenSize.y / 2;
        }

        return new Rect(startX, startY, endX, endY);
    }


    private void restartGame() {
        Intent intent = new Intent(gameContext, SnakeActivity.class);
        gameContext.startActivity(intent);
        // Finish the current activity so that it restarts
        if (gameContext instanceof Activity) {
            ((Activity) gameContext).finish();
        }
    }

    long start = 0;

    @Override
    public boolean onTouchEvent(MotionEvent motionEvent) {

        switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                if (wayMethod == 3) {
                    start = System.currentTimeMillis();
                    new Thread() {
                        @Override
                        public void run() {
                            try {
                                Thread.sleep(300);
                            } catch (InterruptedException ignore) {
                            }
                            if (start != 0) {
                                if (!audioRecordStart.isStart()) {
                                    audioRecordStart.start();
                                }
                            }
                        }
                    }.start();
                }
                break;
            case MotionEvent.ACTION_UP:
                if (wayMethod == 3) {
                    if (audioRecordStart.isStart()) {
                        audioRecordStart.end();
                    }
                    start = 0;
                }
                float touchX = motionEvent.getX();
                float touchY = motionEvent.getY();
                // 检查是否点击了返回按钮
                if (touchX >= 350 && touchX <= (350 + blockSize * 2) && touchY >= 10 && touchY <= (10 + blockSize)) {
                    // 重新运行 onCreate 方法
                    restartGame();
                    return true;  // 事件已处理
                }

                switch (CURRENT_STATE) {

                    case START_SCREEN:
                        CURRENT_STATE = GAME_STATE.TUTORIAL_SCREEN;
                        if (mSongID > 0) {
                            mSP.play(mSongID, .5f, .5f, 0, -1, 1);
                        }
                        return true;
                    case TUTORIAL_SCREEN:
                        CURRENT_STATE = GAME_STATE.PAUSE_SCREEN;
                        if (multiplayer == 0 || multiplayer == 1) {
                            newGame();
                        }
                        return true;
                    case GAME_OVER_SCREEN:
                        CURRENT_STATE = GAME_STATE.PAUSE_SCREEN;
                        if (multiplayer == 0 || multiplayer == 1) {
                            newGame();
                        }
                        return true;
                    case PAUSE_SCREEN:
                        CURRENT_STATE = GAME_STATE.PLAYING_SCREEN;
                        return true;
                }

                if (motionEvent.getX() - pauseButton.getX() <= pauseButton.getButtonSize() && motionEvent.getY() - pauseButton.getY() <= pauseButton.getButtonSize()) {
                    CURRENT_STATE = GAME_STATE.PAUSE_SCREEN;
                    return true;
                }
                // Let the Snake class handle the input
                if (wayMethod == 1) {
                    mSnake.switchHeading(motionEvent);
                }
                break;

            default:
                break;

        }
        return true;
    }

    public void switchHeading(float x, float y) {
        // x < 0 left，x > 0 right
        // y > 0 up, y< down
        Snake.Heading heading = mSnake.getHeading();
        if (Math.abs(x) > Math.abs(y)) {
            if (x > 0) {
                heading = Snake.Heading.RIGHT;
            } else if (x < 0) {
                heading = Snake.Heading.LEFT;
            }
        } else if (Math.abs(x) < Math.abs(y)) {
            if (y > 0) {
                heading = Snake.Heading.UP;
            } else if (y < 0) {
                heading = Snake.Heading.DOWN;
            }
        }

        mSnake.switchHeading(heading);
    }

    // Stop the thread
    public void pause() {
        mPlaying = false;
        try {
            mThread.join();
        } catch (InterruptedException e) {
            // Error
        }
    }

    // Start the thread
    public void resume() {
        mPlaying = true;
        mThread = new Thread(this);
        mThread.start();
    }

    public void deathScreen(Canvas mCanvas) {
        mCanvas.drawBitmap(gameOverScreen, null, new Rect(0, 0, mCanvas.getWidth(), mCanvas.getHeight()), null);

    }

    public void pauseScreen(Canvas mCanvas) {
        mCanvas.drawBitmap(pauseScreen, null, new Rect(0, 0, mCanvas.getWidth(), mCanvas.getHeight()), null);
    }

    public void startScreen(Canvas mCanvas) {
        mCanvas.drawBitmap(startScreen, null, new Rect(0, 0, mCanvas.getWidth(), mCanvas.getHeight()), null);
        mCanvas.drawBitmap(border, null, new Rect(0, 0, mCanvas.getWidth(), mCanvas.getHeight()), null);
    }

    public void tutorialScreen(Canvas mCanvas) {
        mCanvas.drawBitmap(helpScreen, null, new Rect(0, 0, mCanvas.getWidth(), mCanvas.getHeight()), null);
    }

    /**
     * 绘制生命值
     *
     * @param snake  蛇蛇
     * @param canvas 画板
     */
    public void drawLives(Snake snake, Canvas canvas) {
        Bitmap heart = BitmapFactory.decodeResource(gameContext.getResources(), R.drawable.heart);
        heart = Bitmap.createScaledBitmap(heart, blockSize, blockSize, false);
        for (int i = 1; i <= snake.getLives(); i++) {
            canvas.drawBitmap(heart, canvas.getWidth() - blockSize * i, 0, mPaint);
        }
    }

    public void loadBackground(Context context) {
        backGround = BitmapFactory.decodeResource(context.getResources(), R.drawable.background);
        border = BitmapFactory.decodeResource(context.getResources(), R.drawable.border);
        pauseButton = new PauseButton(context, blockSize);
        huo = BitmapFactory.decodeResource(context.getResources(), R.drawable.huo);
        huo = Bitmap.createScaledBitmap(huo, Const.SIZE, Const.SIZE, false);
        wall = BitmapFactory.decodeResource(context.getResources(), R.drawable.wall);
        wall = Bitmap.createScaledBitmap(wall, Const.SIZE, Const.SIZE, false);
    }

    public void loadScreens(Context context) {
        // 开始页
        startScreen = BitmapFactory.decodeResource(context.getResources(), R.drawable.startscreen);
        // 帮助页
        helpScreen = BitmapFactory.decodeResource(context.getResources(), R.drawable.helpscreen);
        // 暂停or待开始
        pauseScreen = BitmapFactory.decodeResource(context.getResources(), R.drawable.pausescreen);
        // 游戏结束页
        gameOverScreen = BitmapFactory.decodeResource(context.getResources(), R.drawable.gameoverscreen);
    }

    public void initializeSoundPool(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build();

            mSP = new SoundPool.Builder().setMaxStreams(5).setAudioAttributes(audioAttributes).build();
        } else {
            mSP = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
        }
        try {
            AssetManager assetManager = context.getAssets();
            AssetFileDescriptor descriptor;

            // Prepare the sounds in memory
            descriptor = assetManager.openFd("EatSound.ogg");
            mEat_ID = mSP.load(descriptor, 0);

            descriptor = assetManager.openFd("DeathSound.ogg");
            mCrashID = mSP.load(descriptor, 0);

            descriptor = assetManager.openFd("Music.ogg");
            mSongID = mSP.load(descriptor, 0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Snake makeSnake(Context context) {
        SharedPreferences sharedPref = context.getSharedPreferences("SnakeGamePrefs", Context.MODE_PRIVATE);
        int skinResource = sharedPref.getInt("selected_skin", R.drawable.body3);
        // 屏幕大小
        Point mr = new Point(screenSize.x, screenSize.y);
        Snake snake = new Snake(context, mr, skinResource);
        snake.name = "game1";
        return snake;
    }

    public int getMaxScore() {
        return snakes.stream().map(Snake::getScore).max(Integer::compareTo).orElse(0);
    }

    public Food getTargetFood(RobotSnake robotSnake) {
        Food targetFood = null;
        Point headPoint = robotSnake.getSegmentLocations().get(0);
        int minDist = Integer.MAX_VALUE;
        for (Food food : FOOD) {
            Point location = food.getLocation();
            int dist = Math.abs(location.x - headPoint.x) + Math.abs(location.y - headPoint.y);
            if (dist < minDist) {
                targetFood = food;
                minDist = dist;
            }
        }
        return targetFood;
    }

    public void loadSnake(Map<String, Message.SnakeData> snakeDataMap) {
        if (snakeDataMap.size() > snakes.size()) {
            Set<String> collect = snakes.stream().map(item -> item.name).collect(Collectors.toSet());
            snakeDataMap.forEach((key, value) -> {
                if (!collect.contains(key)) {
                    snakes.add(new Snake(gameContext, screenSize, value.skinResource));
                }
            });

            for (Snake snake : snakes) {
                Message.SnakeData snakeData = snakeDataMap.get(snake.name);
                snake.setScore(snakeData.score);
                snake.switchHeading(snakeData.heading);
                snake.getSegmentLocations().clear();
                snake.getSegmentLocations().addAll(snakeData.segmentLocations);

            }
        }
    }
}
